<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>麻将游戏</title>
    <script type="text/javascript" src="js/jquery-3.6.0.js"></script>
    <style>
        body {
            min-width: 1887px;
        }

        .room {
            outline: pink 1px solid;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .desk {
            outline: pink 1px solid;
            display: flex;
            align-items: center;
            justify-content: center;
            flex-flow: row wrap;
        }

        .pre, .center, .next {
            outline: pink 1px solid;
            height: 850px;
        }

        .pre, .next {
            flex-grow: 2;
            flex-basis: 374px;
            display: flex;
            flex-direction: row;
        }

        .center {
            display: flex;
            flex-grow: 6;
            flex-direction: column;
            flex-basis: 1128px;
        }

        .opposite, .self {
            flex-grow: 4;
            outline: pink 1px solid;
            flex-basis: 335px;
        }

        .opposite {
            display: flex;
            flex-direction: column;
        }

        .self {
            display: flex;
            flex-direction: column;
            /*position: relative;*/
        }

        .hand, .out {
            outline: pink 1px solid;
        }

        .out {
            flex-grow: 2;
            display: flex;
            flex-direction: row;
            flex-basis: 221px;
        }

        .outCard, .middlePengOrGang {
            outline: pink 1px solid;
        }

        .outCard {
            flex-grow: 5;
            flex-basis: 934px;
            display: flex;
            flex-flow: row wrap;
            align-content: start;
        }

        .middlePengOrGang {
            flex-grow: 1;
            flex-basis: 186px;
            display: flex;
            flex-flow: row wrap;
            align-content: start;
        }

        .pengOrGangImg {
            outline: pink 1px solid;
            width: 40px;
            height: 56px;
        }

        .hand {
            display: flex;
            flex-grow: 1;
            flex-direction: row;
            flex-basis: 110px;
        }

        .card {
            outline: pink 1px solid;
        }

        .handCardImg {
            width: 80px;
            height: 108px;
        }

        .outCardImg {
            width: 50px;
            height: 70px;
        }


        .middle {
            flex-grow: 2;
            outline: pink 1px solid;
            flex-basis: 167px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            position: relative;
        }

        .selfLackType {
            position: absolute;
        }

        .huCardStyle {
            box-shadow: 0 0 5px 6px #09afff;
        }

        .dangerCardStyle {
            outline: red 3px solid;
        }

        .gameControlWrap {
            position: absolute;
            outline: pink solid 1px;
            left: 28%;
            /*bottom: 115px;*/
            bottom: -224px;
        }

        .selfHandCards {
            position: relative;
            /*justify-content: center;*/
        }

        .pengOrGangCardWrap {
            display: flex;
        }

        .gangOrPengBox {
            /*position: absolute;*/
            /*outline: pink 1px solid;*/
            /*top: -69px;*/
            /*left: 50%;*/
        }

        .pengOrGangChose {
            border: blue 2px solid;
        }

        .sideHandCards {
            outline: pink 1px solid;
            flex-grow: 1;
            flex-basis: 125px;
            display: flex;
            flex-direction: column;
        }

        .sideHandCardImg {
            outline: pink 1px solid;
            width: 81px;
            height: 60px;
        }

        .sideOut {
            outline: pink 1px solid;
            flex-grow: 2;
            flex-direction: column;
            display: flex;
            flex-basis: 250px;
        }

        .sideOutCard {
            outline: pink 1px solid;
            flex-grow: 4;
            flex-basis: 680px;
            display: flex;
        }

        .leftOutCard {
            flex-flow: row-reverse wrap;
        }

        .rightOutCard {
            flex-flow: row wrap;
        }

        .sidePengOrGang {
            outline: pink 1px solid;
            flex-grow: 1;
            display: flex;
            flex-basis: 170px;
        }

        .leftPengOrGang {
            flex-flow: row-reverse wrap;
        }

        .rightPengOrGang {
            flex-flow: row wrap;
        }

        .sideOutCardWrap {
            width: 25%;
            display: flex;
            position: relative;
            align-items: center;
            flex-direction: column;
        }

        .sidePengOrGangImg {
            position: absolute;
            height: 62px;
            width: 42px;
        }

        .leftPengOrGangImg {
            transform: rotate(90deg);
        }

        .rightPengOrGangImg {
            transform: rotate(-90deg);
        }

        .rightHuCardImg {
            height: 81px;
            width: 60px;
            transform: rotate(-90deg);
        }

        .leftHuCardImg {
            height: 81px;
            width: 60px;
            transform: rotate(90deg);
        }

        .outlineStyle {
            outline: pink 1px solid;
        }
    </style>
    <script>
        let ipAddr = 'localhost:9090'; // localhost:8080, 192.168.77.154, 120.78.240.142:8686
        let gData;
        let webSocket;
        let socketId; // sessionid
        let roomId; // 所在房间id
        let players = []; // [GamePlayer]
        let selfOrder; // 自己在players中的次序
        let typeMap = new Map();
        let protocolItem = {protocol: "", dataString: ""};
        typeMap.set("TONG", "t");
        typeMap.set("TIAO", "y");
        typeMap.set("WAN", "w");
        typeMap.set("back", "back");
        typeMap.set("backR", "backRotated");
        let alreadyDefineLack = false;
        /**
         * new Object[]{card, outCardPlayer.order}
         */
        let cardOut = [{}, 0];
        /**
         * 选择碰还是杠
         * new Object[]{gangItem[], rival.order}
         */
        let pengOrGangArr = [[], 0];
        /**
         * 可以自摸时为true
         */
        let selfHuControl = false;
        /**
         * 是否该自己出牌
         */
        let selfTurn = false;
        /**
         *
         */
        let boxShadow = ['skyblue 0 -30px inset', 'skyblue -30px 0 inset', 'skyblue 0 30px inset', 'skyblue 30px 0 inset'];

        /**
         * 剩余牌的数量
         */
        let cardsNum = 0;


        /**
         * 连接到webSocket服务
         *
         * @param name 玩家名称
         * @param id 房间id
         */
        function connectWebSocket(name, id) {
            let url = 'ws://' + ipAddr + '/myWebSocket/' + name + "/" + id;
            webSocket = new WebSocket(url);
            webSocket.onopen = function (event) {
                console.log('webSocket已连接');
            };
            webSocket.onclose = function (event) {
                console.log('webSocket已关闭连接');
            };
            webSocket.onmessage = function (event) {
                let data = JSON.parse(event.data);
                console.log('接收到消息 ', data);

                if (Array.isArray(data)) {
                    for (let i = 0; i < data.length; i++) {
                        executeInstruct(data[i]);
                    }
                } else {
                    executeInstruct(data);
                }
            };
            webSocket.onerror = function (event) {
                console.log('WebSocket出现错误！，', event);
            };
        }

        function showBeginButton() {
            if (players.length >= 2) $(".beginGame").removeAttr("hidden");
        }

        function showPlayerName() {
            if (players.length === 2) {
                $(".nameSelf").text(players[selfOrder].name);
                $(".nameOpposite").text(players[(selfOrder + 1) % players.length].name);
            } else {
                let ps = $(".middle").find("p");
                for (let i = 0; i < players.length; i++) {
                    ps.eq(i).text(players[(selfOrder + i) % players.length].name);
                }
            }
        }

        function cardToImgName(card) {
            return typeMap.get(card.type) + card.num;
        }

        /**
         * 添加到手牌中
         */
        function appendToHandCard(handCardTag, card, classes = 'card handCardImg selfCardClickEvent', alt = card.type + card.num) {
            handCardTag.append('<img class="' + classes + '" src="image/' + cardToImgName(card) + '.jpg" alt="' + alt + '">');
        }


        /**
         * 给手牌添加事件的函数
         */
        function outCardEvent() {
            if (!alreadyDefineLack) {
                alert('还没有定缺，不能出牌');
                return;
            }
            if (selfHuControl) {
                alert('请先选择是否自摸/杠牌');
                return;
            }
            if (!selfTurn) {
                alert("还没有轮到自己出牌");
                return;
            }
            // 把当前的牌出到出牌堆
            let selfCard = $(this);
            console.log("出牌 " + selfCard.attr('alt'));
            $(".selfOutCard").append('<img class="outCardImg" src="' + selfCard.attr("src") + '" alt="' + selfCard.attr("alt") + '">');
            selfCard.remove();
            // $(".selfCardClickEvent").unbind('dblclick', outCardEvent);
            selfTurn = false;
            // 排序新发的牌
            let handCards = $(".selfHandCards").find("img");
            let lastCard = handCards.last();
            let alt = lastCard.attr('alt');
            // console.log(handCards);
            let lastType = alt.slice(0, -1);
            let lastNum = alt.slice(-1);
            let i = 0;
            for (; i < handCards.length; i++) {
                let alt = handCards.eq(i).attr('alt');
                if (alt.slice(0, -1) === lastType) break;
            }
            for (; i < handCards.length; i++) {
                let alt = handCards.eq(i).attr('alt');
                if (alt.slice(0, -1) !== lastType || alt.slice(-1) >= lastNum) {
                    handCards.eq(i).before(lastCard);
                    break;
                }
            }


            // 发送给webSocket服务
            alt = selfCard.attr('alt');
            let card = {type: alt.slice(0, -1), num: alt.slice(-1)}
            let ptcItem = {protocol: 'OUT_CARD', dataString: card}
            webSocket.send(JSON.stringify(ptcItem));
        }

        function showCardList(cardArray) {
            let hand = $(".selfHandCards");
            for (let i = 0; i < cardArray.length; i++) {
                appendToHandCard(hand, cardArray[i]);
            }

            hand = $(".oppoHandCards")
            for (let i = 0; i < 13; i++) {
                appendToHandCard(hand, {type: 'back', num: ''}, 'card handCardImg', '#');
            }

            let sideHand;
            if (players.length === 4) sideHand = $(".sideHandCards");
            else if (players.length === 3) sideHand = $(".rightHandCards");
            if (sideHand != null) {
                for (let i = 0; i < 13; i++) {
                    appendToHandCard(sideHand, {type: 'backR', num: ''}, 'sideHandCardImg', '#');
                }
            }
        }

        /**
         * 展示危险牌
         *
         * @param dangerArr [WAN1...]
         */
        function showDangerCardStyle(dangerArr) {
            let clickEvent = $(".selfCardClickEvent");
            clickEvent.unbind('dblclick', outCardEvent);
            clickEvent.dblclick(outCardEvent);
            let dangerSet = new Set(dangerArr);
            let selfCards = $(".selfHandCards").find("img");
            selfCards.each(function () {
                let selfTag = $(this);
                if (dangerSet.has(selfTag.attr("alt"))) selfTag.addClass('dangerCardStyle');
                else selfTag.removeClass('dangerCardStyle');
            });
        }

        /**
         * 对手发了一张牌，给对手的手牌牌增加一张
         *
         * @param order 出牌对手的次序
         */
        function rivalGiveCard(order) {
            $(".middle").css('boxShadow', boxShadow[mapOrder(order)]);
            let outPlayer;
            let index = 2
            if (players.length > 2) index = order > selfOrder ? order - selfOrder : order - selfOrder + players.length;

            if (index === 1) {
                appendToHandCard($(".rightHandCards"), {type: 'backR', num: ''}, 'sideHandCardImg', '#');
                // outPlayer = $(".rightHandCards");
            } else if (index === 2) {
                appendToHandCard($(".oppoHandCards"), {type: 'back', num: ''}, 'card handCardImg', '#');
                // $(".oppoHandCards").append('<img class="card handCardImg" src="image/back.jpg" alt="#">');
            } else {
                appendToHandCard($(".leftHandCards"), {type: 'backR', num: ''}, 'sideHandCardImg', '#');
                // outPlayer = $(".leftHandCards");
            }
        }

        /**
         * 玩家定缺
         */
        function defineLack() {
            // 显示定缺标签
            $(".selfLackType").removeAttr("hidden");
        }

        /**
         * 点击定缺事件
         */
        function submitLackType() {
            let selfLackTag = $(".selfLackType");
            let lackTyp = selfLackTag.find(":radio[name='lack']:checked").val();
            selfLackTag.attr('hidden', 'hidden');
            players[selfOrder].lack = lackTyp;
            protocolItem.protocol = 'RECEIVE_LACK_TYPE';
            protocolItem.dataString = lackTyp;
            webSocket.send(JSON.stringify(protocolItem));
        }

        /**
         * 显示所有玩家的缺牌类型
         */
        function showAllLackType(typeArr) {
            alreadyDefineLack = true;

            let types = [];
            for (let i = 0; i < typeArr.length; i++) {
                if (typeArr[i] === 'TONG') types.push('筒');
                else if (typeArr[i] === 'TIAO') types.push('条');
                else types.push('万');
            }

            if (types.length === 2) {
                $(".nameSelf").append('(' + types[selfOrder] + ')');
                $(".nameOpposite").append('(' + types[(selfOrder + 1) % players.length] + ')');
            } else {
                let ps = $(".middle").find("p");
                for (let i = 0; i < types.length; i++) {
                    ps.eq(i).append('(' + types[(selfOrder + i) % types.length] + ')');
                }
            }
        }

        /**
         * 对手出了一张牌
         * 1 显示牌在对手的出牌区域
         * 2 对手手牌减少1张
         */
        function rivalOutCard(rivalOrderAndCard) {
            let ro = rivalOrderAndCard[0];
            let card = rivalOrderAndCard[1];

            let index = 2;
            if (players.length > 2) index = ro >= selfOrder ? ro - selfOrder : ro - selfOrder + players.length;

            let rival;
            if (index === 1) { // 添加到下家的出牌区域
                rival = $(".rightOutCard");
                addSideOutCard(rival, card, false, true);
                $(".rightHandCards").children(':last').remove();
            } else if (index === 2) {
                rival = $(".oppositeOurCard").append('<img class="outCardImg" src="image/' + cardToImgName(card) + '.jpg" alt="' + card.type + card.num + '">');
                $(".oppoHandCards").children(':last').remove();
            } else { // 添加到上家的出牌区域
                rival = $(".leftOutCard");
                addSideOutCard(rival, card, true, true);
                $(".leftHandCards").children(':last').remove();
            }
        }

        /**
         * 是否要胡
         * @param arr new Object[]{card, outCardPlayer.order}
         */
        function ifHu(arr) {
            // 是否要胡逻辑实现
            // console.log(arr);
            cardOut = arr;
            cardOut[2] = cardOut[1];
            let huButton = $(".huButton");
            huButton.val("胡");
            huButton.removeAttr("hidden");
            $(".passButton").removeAttr("hidden");
        }

        /**
         * 有一个玩家胡牌了，显示胡牌图片到该玩家
         * data = new Object[]{rivalCard, rivalOrder, outCardPlayerOrder} {胡的牌，胡牌玩家次序，出牌玩家次序}
         */
        function playerHu(data) {
            let card = data[0];
            let rivalOrder = data[1];
            let outCardPlayer = data[2];

            let huPlayer;
            let index = mapOrder(rivalOrder);
            let classStr;

            if (index === 1) {
                huPlayer = $(".rightHandCards");
                classStr = 'sideHandCardImg rightHuCardImg';
            }
            else if (index === 2) {
                huPlayer = $(".oppoHandCards");
                classStr = 'card handCardImg';
            }
            else if (index === 3) {
                huPlayer = $(".leftHandCards");
                classStr = 'sideHandCardImg leftHuCardImg';
            }
            else {
                huPlayer = $(".selfHandCards");
                classStr = 'card handCardImg selfCardClickEvent';
            }

            // 如果是自摸，先删除一张牌
            if (huPlayer.children().length % 3 === 2) huPlayer.children(':last').remove();
            else delLastOutCard(outCardPlayer);

            // TODO 2022/4/26: 添加胡的牌到出牌区域
            let ps = $(".middle").find("p");
            ps.eq(index).append('(已胡)');

            appendToHandCard(huPlayer, card, classStr);
        }

        /**
         * 对局结束
         */
        function gameOver() {
            $(".beginGameLabel").removeAttr("hidden");
            $(".gameOver").removeAttr("hidden");
            var beginGame = $(".beginGame");
            beginGame.val('再来一局');
            beginGame.removeAttr('hidden');
        }

        /**
         * 是否要胡
         * @param arr new Object[]{card, selfPlayer.order}
         */
        function ifSelfHu(arr) {
            // 是否要胡逻辑实现
            selfHuControl = true;
            cardOut = arr;
            let huButton = $(".huButton");
            huButton.val("自摸");
            huButton.removeAttr("hidden");
            $(".passButton").removeAttr("hidden");
        }


        /**
         * 选择碰还是杠
         * @param data new Object[]{gangItem, order} order：出牌玩家序号
         */
        function pengOrGang(data) {
            console.log("选择碰或者杠:", data);
            pengOrGangArr[0] = [data[0]];
            pengOrGangArr[1] = data[1];
            cardOut[0] = data[0].card;
            cardOut[1] = data[1];
            showPengOrGangBox([data[0]]);
            $(".pengButton").removeAttr("hidden");
            $(".passButton").removeAttr("hidden");
            if (data[0].type === "NORMAL") $(".gangButton").removeAttr("hidden");
        }

        /**
         * 删除wrap中的牌
         *
         * @param wrap jq选择器
         * @param card 牌
         * @param number 删除数量
         */
        function delHandCard(wrap, card, number) {
            let alt = card.type + card.num;
            let children = wrap.children();

            for (let i = 0; i < children.length; i++) {
                if (number <= 0) break;
                if (alt === children.eq(i).attr('alt')) {
                    children.eq(i).remove();
                    number--;
                }
            }
        }

        /**
         * 将order映射成为以当前用户的次序为初始值0的次序
         */
        function mapOrder(order) {
            let index = selfOrder === order ? 0 : 2;
            if (players.length > 2) index = order >= selfOrder ? order - selfOrder : order - selfOrder + players.length;
            return index;
        }

        /**
         * 显示碰或者杠的牌
         *
         * @param data new Object[]{gangItem, Player.order, outCardPlayerOrder} {牌实体，碰/杠玩家序号，出牌玩家序号}
         * @param ifDel 是否删除手牌
         */
        function showPengOrGang(data, ifDel) {
            let gangItem = data[0];
            let order = data[1];
            let player;
            let index = mapOrder(order);
            if (index === 1) player = $(".rightPengOrGang");
            else if (index === 2) player = $(".oppoPengOrGang");
            else if (index === 3) player = $(".leftPengOrGang");
            else player = $('.selfPengOrGang');

            if (gangItem.type === "NORMAL") {
                addPengCard(gangItem.card, player, index);
                addGangCard(gangItem.card, player, index);
                if (ifDel) delHandCard($(".selfHandCards"), gangItem.card, 3);
                // 删除出牌玩家的出的牌
                delLastOutCard(data[2]);
            } else if (gangItem.type === "PENG") {
                addPengCard(gangItem.card, player, index);
                if (ifDel) delHandCard($(".selfHandCards"), gangItem.card, 2);
                // 删除出牌玩家的出的牌
                delLastOutCard(data[2]);
            } else if (gangItem.type === "DARK") {
                // 暗杠的实现
                addPengCard(gangItem.card, player, index);
                addGangCard(gangItem.card, player, index);
                if (ifDel) delHandCard($(".selfHandCards"), gangItem.card, 4);
            } else { // ADD
                // 加杠的实现
                addGangCard(gangItem.card, player, index);
                if (ifDel) delHandCard($(".selfHandCards"), gangItem.card, 1);
            }

            // 删除出牌玩家的出的牌
            // if (data.length === 3) delLastOutCard(data[2]);
            // else if (!selfHuControl) delLastOutCard(pengOrGangArr[1]);
        }

        /**
         * 显示玩家能碰或者杠的牌
         *
         * @param data [GangItem]
         */
        function showPengOrGangBox(data) {
            let box = $(".gangOrPengBox");
            box.empty();
            box.removeAttr("hidden");
            box.append('<img class="pengOrGangImg pengOrGangChose" src="image/' + cardToImgName(data[0].card) + '.jpg" alt="' + 0 + '">');
            for (let i = 1; i < data.length; i++) {
                box.append('<img class="pengOrGangImg" src="image/' + cardToImgName(data[i].card) + '.jpg" alt="' + i + '">');
            }

            box.children().click(function () {
                $(".gangOrPengBox").children().removeClass("pengOrGangChose");
                $(this).addClass('pengOrGangChose');
            });
        }

        /**
         * 是否暗杠 加杠
         *
         * @param data List<GangItem>
         */
        function ifSelfGang(data) {
            $(".gangButton").removeAttr('hidden');
            $(".passButton").removeAttr("hidden");
            // gangButton需要
            pengOrGangArr[1] = selfOrder;
            pengOrGangArr[0] = data;
            showPengOrGangBox(data);
            // passButton需要
            selfHuControl = true;
            cardOut[0] = data[0];
            cardOut[1] = selfOrder;
        }

        /**
         * 删除次序为order的玩家，出牌区域的最后一张牌
         *
         * @param order 玩家次序
         */
        function delLastOutCard(order) {
            let index = mapOrder(order);

            if (index === 1) {
                $(".rightOutCard").children(':last').children(':last').remove();
            }
            else if (index === 2) {
                $(".oppositeOurCard").children(':last').remove();
            }
            else if (index === 3) {
                $(".leftOutCard").children(':last').children(':last').remove();
            }
            else {
                $(".selfOutCard").children(':last').remove();
            }
        }

        /**
         * 减少对手手牌
         *
         * @param data new Object[]{gangItem, Player.order, outCardPlayerOrder} {牌实体，碰/杠玩家序号，出牌玩家序号}
         */
        function rivalPengOrGang(data) {
            showPengOrGang(data, false);

            let gangItem = data[0];
            let order = data[1];
            let player;
            let index = selfOrder === order ? 0 : 2;
            if (players.length > 2) index = order > selfOrder ? order - selfOrder : order - selfOrder + players.length;

            if (index === 1) player = $(".rightHandCards");
            else if (index === 2) player = $(".oppoHandCards");
            else if (index === 3) player = $(".leftHandCards");
            else player = $('.selfHandCards');

            let n;
            if (gangItem.type === 'PENG')
                n = 2;
            else if (gangItem.type === 'DARK')
                n = 4;
            else if (gangItem.type === 'NORMAL')
                n = 3;
            else // ADD
                n = 1;
            delHandCard(player, {type: '#', num: ''}, n);
        }

        /**
         * 清理所有牌
         */
        function clearAllCard() {
            $(".beginGameContainer").children().attr('hidden', 'hidden');

            $(".sideHandCards").empty();
            $(".sideOutCard").empty();
            $(".sidePengOrGang").empty();

            $(".hand").empty();
            $(".outCard").empty();
            $(".middlePengOrGang").empty();

            // 清除缺牌信息
            showPlayerName();

            // 隐藏对局结束标签
            $(".gameOver").attr("hidden", "hidden");
        }

        /**
         * 出现错误时后端请求前端弹出提示
         *
         * @param dataString
         */
        function errorAlert(dataString) {
            alert(dataString);
            if (dataString === '房间不存在！！！' || dataString === '房间人数已满！！！') {
                hiddenExceptBegin(null); // null 不隐藏
            }
        }

        /**
         * 显示剩余牌标签
         */
        function showLastNum() {
            // 显示剩余牌标签
            let lastNum = $(".lastNum");
            lastNum.removeAttr("hidden");
            cardsNum = 108 - 13 * players.length;
            lastNum.text("剩余牌量：" + cardsNum);
        }

        /**
         * 更新剩余牌量
         */
        function updateLastNum() {
            $(".lastNum").text("剩余牌量：" + --cardsNum);
        }

        /**
         * 执行指令
         */
        function executeInstruct(instruct) {
            switch (instruct.protocol) {
                case "SEND_SESSION_ID":
                    socketId = instruct.data.sessionId;
                    selfOrder = instruct.data.order;
                    break;
                case "SEND_ROOM_ID":
                    roomId = instruct.data;
                    $(".roomId").text(roomId);
                    break;
                case "SEND_PLAYERS":
                    players = instruct.data;
                    showBeginButton();
                    showPlayerName();
                    break;
                case "ERROR_ALERT":
                    errorAlert(instruct.dataString)
                    break;
                case "BEGIN_GAME_SEND_SELF_CARDS":
                    clearAllCard();
                    showCardList(instruct.data);
                    showLastNum();
                    break;
                case "SEND_DANGER_CARDS":
                    console.log("接收危险牌 ", instruct.data);
                    showDangerCardStyle(instruct.data);
                    break;
                case "DISTRIBUTE_ONE_CARD":
                    $(".middle").css('boxShadow', boxShadow[mapOrder(selfOrder)]);
                    appendToHandCard($(".selfHandCards"), instruct.data);
                    let clickEvent = $(".selfCardClickEvent");
                    clickEvent.unbind('dblclick', outCardEvent);
                    clickEvent.dblclick(outCardEvent);
                    selfTurn = true;
                    updateLastNum();
                    break;
                case "RIVAL_GIVE_CARD":
                    rivalGiveCard(instruct.data);
                    updateLastNum();
                    break;
                case "DEFINE_LACK":
                    defineLack();
                    break;
                case "SEND_ALL_LACK_TYPE":
                    showAllLackType(instruct.data);
                    break;
                case "RIVAL_OUT_CARD":
                    rivalOutCard(instruct.data);
                    break;
                case "IF_HU":
                    ifHu(instruct.data);
                    break;
                case "PLAYER_HU":
                    playerHu(instruct.data);
                    break;
                case "GAME_OVER":
                    gameOver();
                    $(".lastNum").attr("hidden", "hidden");
                    break;
                case "IF_SELF_HU":
                    ifSelfHu(instruct.data);
                    break;
                case "PENG_OR_GANG":
                    pengOrGang(instruct.data);
                    break;
                case "RIVAL_PENG_OR_GANG":
                    rivalPengOrGang(instruct.data);
                    break;
                case "IF_SELF_GANG":
                    ifSelfGang(instruct.data);
                    break;
                default:
                    console.log("❌: 接收消息时，指令解析错误！！！")
            }
        }

        /**
         * 玩家选择碰时给玩家添加碰的牌到显示区域
         *
         * @param card 碰的牌
         * @param wrap 添加碰的牌的区域
         * @param index 玩家的位置索引（以当前玩家0开始计数）
         */
        function addPengCard(card, wrap, index) {
            if (index === 1) {
                // 右边的玩家
                addSideOutCard(wrap, card, false, false, true, 3);
            } else if (index === 3) {
                // 左边的玩家
                addSideOutCard(wrap, card, true, false, true, 3);
            } else {
                // 自己和对家
                wrap.append('' +
                    '                    <div class="pengOrGangCardWrap">\n' +
                    '                        <img class="pengOrGangImg" src="image/' + cardToImgName(card) + '.jpg" alt="' + card.type + card.num + '">\n' +
                    '                        <img class="pengOrGangImg" src="image/' + cardToImgName(card) + '.jpg" alt="' + card.type + card.num + '">\n' +
                    '                        <img class="pengOrGangImg" src="image/' + cardToImgName(card) + '.jpg" alt="' + card.type + card.num + '">\n' +
                    '                    </div>');
            }
        }

        /**
         * 杠牌时添加杠的牌到显示区域
         *
         * @param card 显示的牌
         * @param wrap 显示的区域
         * @param index 玩家的位置索引（以当前玩家0开始计数）
         */
        function addGangCard(card, wrap, index) {
            if (index === 1) {
                // 右边的玩家
                addSideOutCard(wrap, card, false);
            } else if (index === 3) {
                // 左边的玩家
                addSideOutCard(wrap, card, true);
            } else {
                let alt = card.type + card.num;
                wrap.children().each(function () {
                    let cur = $(this);
                    if (cur.children(":first").attr("alt") === alt)
                        cur.append('<img class="pengOrGangImg" src="image/' + cardToImgName(card) + '.jpg" alt="' + card.type + card.num + '">\n');
                });
            }

        }

        /**
         * 找到选中的要碰或者要胡的牌
         */
        function findCheckedPengOrGang() {
            let res = 0;
            $(".gangOrPengBox").children().each(function () {
                let selfTag = $(this);
                if (selfTag.hasClass('pengOrGangChose')) res = selfTag.attr('alt');
            });

            return res;
        }

        /**
         * 给两边的用户添加打出去的牌
         *
         * @param wrap jq选择器
         * @param card 牌
         * @param left 是否是左边的玩家
         * @param outCard 是不是出牌区域
         * @param newLine 是否是新的一行
         * @param num 添加的数量
         */
        function addSideOutCard(wrap = $(".leftPengOrGang"),
                                card = {
                                    type: 'WAN',
                                    num: 2
                                }, left = true, outCard = false, newLine = false, num = 1) {
            if (num <= 0) return;
            let max = 4; // 碰牌区域
            if (outCard) max = 16; // 出牌区域

            let imgWraps = wrap.children();
            let lastImgWrap = imgWraps.last();
            if (imgWraps.length === 0 || lastImgWrap.children().length >= max || newLine) {
                wrap.append('<div class="sideOutCardWrap"></div>');
                lastImgWrap = wrap.children(':last');
            }

            // 加杠时在碰牌中添加牌
            if (!outCard && !newLine) {
                let alt = card.type + card.num;
                imgWraps.each(function () {
                    let curImgWrap = $(this);
                    if (curImgWrap.children(':first').attr('alt') === alt) lastImgWrap = curImgWrap;
                });
            }

            let position = -9;
            let imgs = lastImgWrap.children();
            let cssProperty = left ? 'top' : 'bottom';
            if (imgs.length > 0) position = Number(imgs.last().css(cssProperty).slice(0, -2)) + 42;
            for (let i = 0; i < num; i++) {
                lastImgWrap.append('<img class="sidePengOrGangImg ' + (left ? "left" : "right") + 'PengOrGangImg" src="image/' + cardToImgName(card) + '.jpg" style="' + cssProperty + ': ' + position + 'px" alt="' + card.type + card.num + '">');
                position += 42;
            }
        }

        /**
         * 隐藏除开始游戏以外的按钮
         */
        function hiddenExceptBegin(attr = 'hidden') {
            let children = $(".beginGameContainer").children();
            for (let i = 1; i < children.length; i++) {
                children.eq(i).attr('hidden', attr);
            }
        }

        $(
            function () {
                $(".creatRoom").click(function () {
                    let name = $('.playerName').val();
                    if (name === '' || name === null) {
                        alert('名称不能为空！');
                        return;
                    }
                    hiddenExceptBegin();
                    connectWebSocket(name, '0');
                });

                $(".joinRoom").click(function () {
                    let name = $('.playerName').val();
                    let roomId = $(".middleRoomId").val();
                    if (name === '' || name === null) {
                        alert('名称不能为空！');
                        return;
                    }
                    if (roomId === '' || roomId === null) {
                        alert('房间号不能为空！');
                        return;
                    }
                    hiddenExceptBegin();
                    connectWebSocket(name, roomId);
                });

                $("#test1").click(function () {
                    // let wrap = $(".gameControlWrap");
                    let wrap = $(".beginGameContainer");
                    if (wrap.children().attr("hidden") == null || wrap.children().attr("hidden") === undefined)
                        wrap.children().attr("hidden", "hidden")
                    else
                        wrap.children().removeAttr("hidden");
                });

                $("#test3").click(function () {
                    let btn = $(".beginGame");
                    if (btn.attr('hidden') === 'hidden') btn.removeAttr('hidden');
                    else btn.attr('hidden', 'hidden');
                    $(".beginGameContainer").removeAttr('hidden');
                });

                $("#test2").click(function () {
                    protocolItem.protocol = 'TEST';
                    webSocket.send(JSON.stringify(protocolItem));
                });

                $(".beginGame").click(function () {
                    $(".beginGameContainer").children().attr('hidden', 'hidden');
                    let instructTo = {};
                    instructTo.protocol = "BEGIN_GAME";
                    webSocket.send(JSON.stringify(instructTo));
                });

                $(".huButton").click(function () {
                    $('.gameControlWrap').children().attr("hidden", "hidden");
                    if (selfHuControl) {
                        cardOut[2] = selfOrder;
                    }
                    selfHuControl = false;
                    protocolItem.protocol = "HU";
                    protocolItem.dataString = cardOut;
                    webSocket.send(JSON.stringify(protocolItem));
                    cardOut[1] = selfOrder;
                    playerHu(cardOut);
                });

                $(".pengButton").click(function () {
                    $('.gameControlWrap').children().attr("hidden", "hidden");
                    // 碰事件
                    let item = pengOrGangArr[0][findCheckedPengOrGang()];
                    item.type = 'PENG';
                    showPengOrGang([item, selfOrder, pengOrGangArr[1]], true);

                    protocolItem.protocol = 'PLAYER_PENG_OR_GANG';
                    protocolItem.dataString = item;
                    webSocket.send(JSON.stringify(protocolItem));
                    // 再出一张牌
                    selfTurn = true;
                });

                $(".gangButton").click(function () {
                    $('.gameControlWrap').children().attr("hidden", "hidden");
                    // 杠事件
                    selfHuControl = false;
                    let item = pengOrGangArr[0][findCheckedPengOrGang()];
                    showPengOrGang([item, selfOrder, pengOrGangArr[1]], true);

                    protocolItem.protocol = 'PLAYER_PENG_OR_GANG';
                    protocolItem.dataString = item;
                    webSocket.send(JSON.stringify(protocolItem));
                });

                $(".passButton").click(function () {
                    $('.gameControlWrap').children().attr("hidden", "hidden");
                    console.log("pass:" + JSON.stringify(cardOut));

                    if (selfHuControl) {
                        selfHuControl = false;
                    } else {
                        protocolItem.protocol = 'PASS';
                        protocolItem.dataString = cardOut;
                        webSocket.send(JSON.stringify(protocolItem));
                    }
                });

                $(".submitIpAddr").click(function () {
                    let ip = $(".ipAddr");
                    ipAddr = ip.val();
                    ip.val("");
                    ip.attr('placeholder', ipAddr);
                    alert('ip地址切换成功');
                });

                $(".ipAddr").attr("placeholder", ipAddr);
            }
        );
    </script>
</head>

<body>
<!--房间信息-->
<section class="room">
    <p>房间号：<span class="roomId">NULL</span></p>
</section>
<!--玩家信息-->
<section class="desk">
    <div class="pre left">
        <div class="sideHandCards leftHandCards">
            <!--            <img class="sideHandCardImg" src="image/backRotated.jpg" alt="#">-->
        </div>
        <div class="sideOut">
            <div class="sideOutCard leftOutCard">
                <!--                <div class="sideOutCardWrap">-->
                <!--                    <img class="sidePengOrGangImg leftPengOrGangImg" src="image/w1.jpg" style="top: -9px" alt="#">-->
                <!--                    <img class="sidePengOrGangImg leftPengOrGangImg" src="image/w1.jpg" style="top: 33px" alt="#">-->
                <!--                    <img class="sidePengOrGangImg leftPengOrGangImg" src="image/w1.jpg" style="top: 75px" alt="#">-->
                <!--                </div>-->
            </div>
            <div class="sidePengOrGang leftPengOrGang">
                <!--                <div class="sideOutCardWrap">-->
                <!--                    <img class="sidePengOrGangImg leftPengOrGangImg" src="image/w1.jpg" style="top: -9px" alt="#">-->
                <!--                    <img class="sidePengOrGangImg leftPengOrGangImg" src="image/w1.jpg" style="top: 33px" alt="#">-->
                <!--                    <img class="sidePengOrGangImg leftPengOrGangImg" src="image/w1.jpg" style="top: 75px" alt="#">-->
                <!--                    <img class="sidePengOrGangImg leftPengOrGangImg" src="image/w1.jpg" style="top: 117px" alt="#">-->
                <!--                </div>-->
            </div>
        </div>
    </div>
    <div class="center">
        <div class="opposite">
            <div class="hand oppoHandCards">
                <!--                <img class="card handCardImg" src="image/back.jpg" alt="#">-->
            </div>
            <div class="out">
                <div class="middlePengOrGang oppoPengOrGang"></div>
                <div class="outCard oppositeOurCard">
                    <!--                    <img class="outCardImg" src="image/w1.jpg" alt="#">-->
                </div>
            </div>
        </div>
        <div class="middle">
            <p class="nameSelf" style="position: absolute; bottom: 0; margin: 0;"></p>
            <p class="nameRight" style="position: absolute; right: 0"></p>
            <p class="nameOpposite" style="position: absolute; top: 0;  margin: 0;"></p>
            <p class="nameLeft" style="position: absolute; left: 0"></p>
            <h3 class="gameOver" hidden>对局结束！</h3>
            <h3 class="lastNum" hidden>剩余牌量：0</h3>
            <span class="selfLackType" style="top: 372px;" hidden>
                    <label>
                        筒:<input type="radio" name="lack" value="TONG"/>&nbsp;&nbsp;
                        条:<input type="radio" name="lack" value="TIAO"/>&nbsp;&nbsp;
                        万:<input type="radio" name="lack" value="WAN"/>&nbsp;&nbsp;
                        <input type="submit" value="提交" onclick="submitLackType()">
                    </label>
            </span>
            <span class="gameControlWrap">
                <span class="gangOrPengBox">
<!--                    <img class="pengOrGangImg pengOrGangChose" src="image/w1.jpg" alt="#">-->
                </span>
                <input type="button" class="pengButton" value="碰" hidden>
                <input type="button" class="gangButton" value="杠" hidden>
                <input type="button" class="huButton" value="胡" hidden>
                <input type="button" class="passButton" value="过" hidden>
            </span>
            <div class="beginGameContainer">
                <label class="beginGameLabel">
                    <input hidden type="button" value="开始游戏" class="beginGame">
                </label>
                <label>
                    昵称：<input type="text" class="playerName">
                </label>
                <label>
                    <input type="submit" value="创建房间" class="creatRoom">
                </label>
                <label>||</label>
                <label>
                    房间号：<input type="text" class="middleRoomId">
                </label>
                <label>
                    <input type="submit" value="加入房间" class="joinRoom">
                </label>
            </div>
        </div>
        <div class="self">
            <div class="out">
                <div class="outCard selfOutCard">
                    <!--                    <img class="outCardImg" src="image/w1.jpg" alt="#">-->
                </div>
                <div class="middlePengOrGang selfPengOrGang">
                </div>
            </div>
            <div class="hand selfHandCards">
                <!--<img class="card handCardImg selfCardClickEvent" src="image/w1.jpg" alt="#">-->
            </div>

        </div>
    </div>
    <div class="next right">
        <div class="sideOut">
            <div class="sidePengOrGang rightPengOrGang">
                <!--                <div class="sideOutCardWrap">-->
                <!--                    <img class="sidePengOrGangImg rightPengOrGangImg" src="image/w1.jpg" style="top: -9px" alt="#">-->
                <!--                    <img class="sidePengOrGangImg rightPengOrGangImg" src="image/w1.jpg" style="top: 33px" alt="#">-->
                <!--                    <img class="sidePengOrGangImg rightPengOrGangImg" src="image/w1.jpg" style="top: 75px" alt="#">-->
                <!--                    <img class="sidePengOrGangImg rightPengOrGangImg" src="image/w1.jpg" style="top: 117px" alt="#">-->
                <!--                </div>-->
            </div>
            <div class="sideOutCard rightOutCard">
                <!--                <div class="sideOutCardWrap">-->
                <!--                    <img class="sidePengOrGangImg rightPengOrGangImg" src="image/w1.jpg" style="bottom: -9px" alt="#">-->
                <!--                    <img class="sidePengOrGangImg rightPengOrGangImg" src="image/w1.jpg" style="bottom: 33px" alt="#">-->
                <!--                    <img class="sidePengOrGangImg rightPengOrGangImg" src="image/w1.jpg" style="bottom: 75px" alt="#">-->
                <!--                </div>-->
            </div>
        </div>
        <div class="sideHandCards rightHandCards"></div>
    </div>
</section>
<section class="test">
    <input hidden type="button" value="test1" id="test1">
    <input hidden type="button" value="test3" id="test3">
    <input hidden type="button" value="发送测试消息" id="test2">
    <span>
        <label>
            更换WebSocket地址：<input class="ipAddr" type="text"  placeholder="120.78.240.142:8686">
            <input class="submitIpAddr" type="submit" value="提交">
        </label>
    </span>
</section>
</body>
</html>